namespace NetFx.AGIL
{
  using System;
  using System.Collections.Generic;
  using System.Text;
  using System.IO;
  using System.Reflection;
  using System.Reflection.Emit;

  public class AgilGeneratorReader : AgilCodeReader
  {
    #region Statics
    public new static AgilGeneratorReader of(MethodBase method)
    {
      return new AgilGeneratorReader(method);
    }
    public new static AgilGeneratorReader of(Module module, MethodBody body)
    {
      return new AgilGeneratorReader(module, body);
    }
    #endregion
    #region Fields
    protected MethodBody body = null;
    #endregion
    #region Properties
    public virtual MethodBody Body
    {
      get
      {
        return this.body;
      }
    }
    public virtual bool HasBody
    {
      get
      {
        return this.body != null;
      }
    }
    public virtual IList<ExceptionHandlingClause> SEHList
    {
      get
      {
        return this.HasBody ? this.Body.ExceptionHandlingClauses : null;
      }
    }
    #endregion
    #region Constructors
    public AgilGeneratorReader(MethodBase method)
      : base(method)
    {
      if (method != null)
      {
        this.body = method.GetMethodBody();
      }
    }
    public AgilGeneratorReader(Module module, MethodBody body)
      : base(module, body)
    {
      this.body = body;
    }
    #endregion
    #region Methods

    public virtual bool SymanticParse(List<AgilOperation> operations)
    {
      return this.SymanticParse(operations, true);
    }

    public virtual bool SymanticParse(List<AgilOperation> operations, bool removeExceptionInstructions)
    {
      if (Parse(operations))
      {
        this.BuildFakeOperations(operations);

        if (removeExceptionInstructions)
        {
          this.RemoveExceptionInstructions(operations);
        }

        return true;
      }
      return false;
    }

    public virtual bool Parse(List<AgilOperation> operations)
    {
      int index = 0;

      return this.Parse(operations, ref index);
    }
    public virtual bool Parse(List<AgilOperation> operations,ref int index)
    {
      if (operations != null)
      {
        AgilOperation agil = null;
        do
        {
          agil = this.Read();

          if (agil != null)
          {
            operations.Add(agil);
            index++;
          }

        }
        while (agil != null);

      }
      return true;
    }

    protected virtual bool IsExceptionInstruction(AgilOperation agil)
    {
      if (agil != null)
      {
        if (agil.IsOpcodeMatched(AgilOperation.Leave)
          || agil.IsOpcodeMatched(AgilOperation.Leave_S)
          || agil.IsOpcodeMatched(AgilOperation.Endfilter)
          || agil.IsOpcodeMatched(AgilOperation.Endfinally)
          )
        {
          return true;
        }
      }
      return false;
    }


    protected virtual bool RemoveExceptionInstructions(List<AgilOperation> list)
    {
      List<AgilOperation> results = null;
      if (list != null)
      {
        if ((results = new List<AgilOperation>()) != null)
        {
          foreach (AgilOperation agil in list)
          {
            if (!this.IsExceptionInstruction(agil))
            {
              results.Add(agil);
            }
          }

          list.Clear();

          list.AddRange(results);

        }
      }
      return true;
    }
    protected virtual bool BuildCatchBlock(List<AgilOperation> list, ExceptionHandlingClause ehc)
    {
      if (ehc!=null)
      {
        AgilOperation tryOperation = AgilOperation.Try.Clone();
        AgilOperation tryEndOperation = AgilOperation.TryEnd.Clone();
        AgilOperation catchOperation = AgilOperation.Catch.Clone();

        if (tryOperation != null && tryEndOperation != null && catchOperation != null)
        {
          //with catch type.
          catchOperation.TypeOperand = ehc.CatchType;
          //.endtry
          this.InsertOperation(list, ehc.HandlerOffset + ehc.HandlerLength, tryEndOperation);
          //.catch
          this.InsertOperation(list, ehc.HandlerOffset, catchOperation);
          //.try
          this.InsertOperation(list, ehc.TryOffset, tryOperation);

          return true;
        }
      }
      return false;
    }
    protected virtual bool BuildFaultBlock(List<AgilOperation> list, ExceptionHandlingClause ehc)
    {
      if (ehc != null)
      {
        AgilOperation tryOperation = AgilOperation.Try.Clone();
        AgilOperation tryEndOperation = AgilOperation.TryEnd.Clone();
        AgilOperation faultOperation = AgilOperation.Fault.Clone();

        if (tryOperation != null && tryEndOperation != null && faultOperation != null)
        {
          //.endtry
          this.InsertOperation(list, ehc.HandlerOffset + ehc.HandlerLength, tryEndOperation);
          //.fault
          this.InsertOperation(list, ehc.HandlerOffset, faultOperation);
          //.try
          this.InsertOperation(list, ehc.TryOffset, tryOperation);

          return true;
        }
      }
      return false;
    }

    protected virtual bool BuildFilterBlock(List<AgilOperation> list, ExceptionHandlingClause ehc)
    {
      if (ehc != null)
      {
        AgilOperation tryOperation = AgilOperation.Try.Clone();
        AgilOperation tryEndOperation = AgilOperation.TryEnd.Clone();
        AgilOperation filterOperation = AgilOperation.Filter.Clone();

        if (tryOperation != null && tryEndOperation != null && filterOperation != null)
        {
          //.endtry
          this.InsertOperation(list, ehc.HandlerOffset + ehc.HandlerLength, tryEndOperation);
          //.filter
          this.InsertOperation(list, ehc.FilterOffset, filterOperation);
          //.try
          this.InsertOperation(list, ehc.TryOffset, tryOperation);

          return true;
        }
      }
      return false;
    }

    protected virtual bool BuildFinallyBlock(List<AgilOperation> list, ExceptionHandlingClause ehc)
    {
      if (ehc != null)
      {
        AgilOperation tryOperation = AgilOperation.Try.Clone();
        AgilOperation tryEndOperation = AgilOperation.TryEnd.Clone();
        AgilOperation finallyOperation = AgilOperation.Finally.Clone();

        if (tryOperation != null && tryEndOperation != null && finallyOperation != null)
        {
          //.endtry
          this.InsertOperation(list, ehc.HandlerOffset + ehc.HandlerLength, tryEndOperation);
          //.finally
          this.InsertOperation(list, ehc.HandlerOffset, finallyOperation);
          //.try
          this.InsertOperation(list, ehc.TryOffset, tryOperation);

          return true;
        }

      }
      return false;
    }
    protected virtual List<AgilOperation> BuildFakeOperations(List<AgilOperation> list)
    {
      if (list != null)
      {
        IList<ExceptionHandlingClause> SEHList = this.SEHList;

        if (SEHList != null)
        {
          foreach (ExceptionHandlingClause ehc in SEHList)
          {
            if (ehc != null)
            {              
              switch (ehc.Flags)
              {
                case ExceptionHandlingClauseOptions.Clause:
                  this.BuildCatchBlock(list, ehc);
                  break;
                case ExceptionHandlingClauseOptions.Fault:
                  this.BuildFaultBlock(list, ehc);
                  break;
                case ExceptionHandlingClauseOptions.Filter:
                  this.BuildFilterBlock(list, ehc);
                  break;
                case ExceptionHandlingClauseOptions.Finally:
                  this.BuildFinallyBlock(list, ehc);
                  break;
                default:
                  break;
              }
            }
          }
        }
      }
      return list;
    }
    protected virtual bool InsertOperation(List<AgilOperation> list, long position, AgilOperation insertAgil)
    {
      return InsertOperation(list, position, insertAgil, true);
    }

    protected virtual bool InsertOperation(List<AgilOperation> list, long position, AgilOperation insertAgil, bool singleton)
    {
      if (list != null)
      {
        int index = this.FindPosition(list, position);
        if (index >= 0)
        {
          AgilOperation agil = list[index];

          if (agil != null)
          {
            if (agil.IsOpcodeMatched(insertAgil) )
            {
              if (!singleton)
              {
                //Prepose
                insertAgil.OperationOffset = position;
                list.Insert(index, insertAgil);
                return true;
              }
            }
            else
            {
              //Prepose
              insertAgil.OperationOffset = position;
              list.Insert(index, insertAgil);
              return true;
            }
          }
        }
      }
      return false;
    }

    protected virtual int FindPosition(List<AgilOperation> list, long position)
    {
      if (list != null)
      {
        for (int i = 0; i < list.Count; i++)
        {
          AgilOperation agil = list[i];

          if (agil != null && agil.OperationOffset == position)
          {
            return i;
          }
        }
      }
      return -1;
    }
    #endregion
  }
}
